﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Asol.Reporting.Support.Data
{
    /// <summary>
    /// Collection : kolekce dat včetně funkcionality.
    /// Potomek musí přepsat abstraktní metodu GetName(), může využít virtuální metodu CheckItemBeforeAdd().
    /// Může definovat StringComparison pro jméno - v konstruktoru base(StringComparison).
    /// </summary>
    public abstract class Collection<T> : IEnumerable<T>
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Collection()
        {
            this._Items = new List<T>();
            this.NameComparison = StringComparison.Ordinal;
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="nameComparison">Režim pro vyhledání položky dle názvu. Default = Ordinal.</param>
        public Collection(StringComparison nameComparison)
        {
            this._Items = new List<T>();
            this.NameComparison = nameComparison;
        }
        /// <summary>
        /// Pole položek
        /// </summary>
        protected List<T> _Items;
        /// <summary>
        /// Režim vyhledání podle jména
        /// </summary>
        protected StringComparison NameComparison = StringComparison.Ordinal;
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Collection of " + typeof(T).Name + ": " + this.Count.ToString() + " items";
        }
        #endregion
        #region Abstract a virtual
        /// <summary>
        /// Abstraktní metoda kolekce, která vrátí klíčový název z dané položky
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        protected abstract string GetName(T item);
        /// <summary>
        /// Prostor pro metodu na potomkovi, která prověří korektnost položky dat před jejím přidáním do kolekce.
        /// Metoda nemá za úkol prověřovat duplicitu podle názvu, to proběhne automaticky následovně.
        /// Zde není jisto, zda prvek bude do kolekce přidán, to bude jisto v metodách ItemAddBefore() a ItemAddAfter()
        /// </summary>
        /// <param name="item"></param>
        protected virtual void CheckItemBeforeAdd(T item) { }
        /// <summary>
        /// Před přidáním daného prvku do kolekce
        /// </summary>
        /// <param name="item"></param>
        protected virtual void OnItemAddBefore(T item) { }
        /// <summary>
        /// Po přidání daného prvku do kolekce
        /// </summary>
        /// <param name="item"></param>
        protected virtual void OnItemAddAfter(T item) { }
        /// <summary>
        /// Před odebráním daného prvku z kolekce
        /// </summary>
        /// <param name="item"></param>
        protected virtual void OnItemRemoveBefore(T item) { }
        /// <summary>
        /// Po odebrání daného prvku z kolekce
        /// </summary>
        /// <param name="item"></param>
        protected virtual void OnItemRemoveAfter(T item) { }
        /// <summary>
        /// true pokud se má ignorovat duplicita názvů. Pak index dle názvu vrátí první odpovídající prvek, ostatní se shodným názvem ignoruje.
        /// </summary>
        protected virtual bool IgnoreNameDuplicity { get { return false; } }
        #endregion
        #region Public property
        /// <summary>
        ///Jednotlivé položky
        /// </summary>
        public IEnumerable<T> Items { get { return this._Items; } }
        /// <summary>
        /// Příznak, zda je kolekce prázdná
        /// </summary>
        public bool IsEmpty { get { return (this._Items.Count == 0); } }
        /// <summary>
        /// Počet položek v kolekci
        /// </summary>
        public int Count { get { return this._Items.Count; } }
        /// <summary>
        /// Vrátí položku z daného indexu.
        /// Index mimo rozsah vyhodí chybu.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T this[int index]
        {
            get { return this._Items[index]; }
        }
        /// <summary>
        /// Vrátí položku daného názvu.
        /// Neexistující název vyhodí chybu.
        /// Porovnání názvu probíhá podle StringComparison zadaného v konstruktoru, default = Ordinal.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public T this[string name]
        {
            get { return this.FindByName(name); }
        }
        #endregion
        #region Events, handler, args
        /// <summary>Událost před přidáním prvku do kolekce</summary>
        public event CollectionEventHandler ItemAddBefore;
        /// <summary>Událost po přidání prvku do kolekce</summary>
        public event CollectionEventHandler ItemAddAfter;
        /// <summary>Událost před odebráním prvku z kolekce</summary>
        public event CollectionEventHandler ItemRemoveBefore;
        /// <summary>Událost po odebrání prvku z kolekce</summary>
        public event CollectionEventHandler ItemRemoveAfter;
        private void _OnItemAddBefore(T item)
        {
            if (this.ItemAddBefore != null)
                this.ItemAddBefore(this, new CollectionEventArgs(item));
            this.OnItemAddBefore(item);
        }
        private void _OnItemAddAfter(T item)
        {
            if (this.ItemAddAfter != null)
                this.ItemAddAfter(this, new CollectionEventArgs(item));
            this.OnItemAddAfter(item);
        }
        private void _OnItemRemoveBefore(T item)
        {
            if (this.ItemRemoveBefore != null)
                this.ItemRemoveBefore(this, new CollectionEventArgs(item));
            this.OnItemRemoveBefore(item);
        }
        private void _OnItemRemoveAfter(T item)
        {
            if (this.ItemRemoveAfter != null)
                this.ItemRemoveAfter(this, new CollectionEventArgs(item));
            this.OnItemRemoveAfter(item);
        }
        /// <summary>
        /// Předpis handleru událostí v Collection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public delegate void CollectionEventHandler(object sender, CollectionEventArgs args);
        /// <summary>
        /// Data pro události v Collection
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class CollectionEventArgs : EventArgs
        {
            /// <summary>
            /// Konstruktor
            /// </summary>
            /// <param name="item"></param>
            public CollectionEventArgs(T item)
            {
                this.Item = item;
            }
            /// <summary>
            /// Položka o kterou se jedná
            /// </summary>
            public T Item { get; private set; }
        }
        #endregion
        #region Vyhledání podle názvu
        /// <summary>
        /// Najde a vrátí položku daného jména.
        /// Pokud nenajde, vyhodí chybu.
        /// Porovnání názvu probíhá podle StringComparison zadaného v konstruktoru, default = Ordinal.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public T FindByName(string name)
        {
            if (String.IsNullOrEmpty(name))
                throw new DevException("Nelze hledat položku podle jména bez zadání jména.");
            
            T item;
            if (!this.TryFindByName(name, out item))
                throw new DevException("Položka jména %0 v seznamu neexistuje.", name);

            return item;
        }
        /// <summary>
        /// Vyhledá a vrátí seznam položek, jejichž jméno je požadováno.
        /// Některé kolekce totiž dovolují, aby v kolekci bylo více položek stejného jména.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public IEnumerable<T> FindAllByName(string name)
        {
            if (String.IsNullOrEmpty(name))
                throw new DevException("Nelze hledat položku podle jména bez zadání jména.");

            return this._Items.Where(p => String.Equals(GetName(p), name, this.NameComparison));
        }
        /// <summary>
        /// Vrátí true, pokud kolekce obsahuje položku daného jména
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool ContainsIndex(int index)
        {
            return (index >= 0 && index < this.Count);
        }
        /// <summary>
        /// Vrátí true, pokud kolekce obsahuje položku daného jména
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool ContainsName(string name)
        {
            T item;
            return this.TryFindByName(name, out item);
        }
        /// <summary>
        /// Zkusí najít položku daného jména.
        /// Vrací true = existuje. Hodnotu dává do out parametru.
        /// Porovnání názvu probíhá podle StringComparison zadaného v konstruktoru, default = Ordinal.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool TryFindByName(string name, out T item)
        {
            item = this._Items.FirstOrDefault(p => String.Equals(GetName(p), name, this.NameComparison));
            return (item != null);
        }
        #endregion
        #region Přidávání a nulování položek
        /// <summary>
        /// Přidá další položku do kolekce.
        /// </summary>
        /// <param name="item"></param>
        public void Add(T item)
        {
            this._ItemAdd(item);
        }
        /// <summary>
        /// Přidá další položky do kolekce.
        /// </summary>
        /// <param name="items"></param>
        public void AddRange(IEnumerable<T> items)
        {
            if (items == null) return;
            foreach (T item in items)
                this._ItemAdd(item);
        }
        /// <summary>
        /// Import položek z dodaného zdroje.
        /// Import nejprve smaže všechny stávající položky, a pak do sebe přidá položky dodané.
        /// </summary>
        /// <param name="items"></param>
        public void Import(IEnumerable<T> items)
        {
            this.Clear();
            if (items == null) return;
            foreach (T item in items)
                this._ItemAdd(item);
        }
        /// <summary>
        /// Odebere dané položky.
        /// Dané položky musí být tytéž objekty, které jsou uloženy v kolekci (shoda položek se určuje metodou Object.ReferenceEquals()).
        /// Tuto metodu tedy není možno použít v případě položky typu structure (Value type).
        /// </summary>
        /// <param name="items"></param>
        public void Remove(IEnumerable<T> items)
        {
            if (items == null) return;
            foreach (T item in items)
                this.Remove(item);
        }
        /// <summary>
        /// Odebere danou položku.
        /// Daná položky musí být identický objekt, který je uložen v kolekci (shoda položek se určuje metodou Object.ReferenceEquals()).
        /// Tuto metodu tedy není možno použít v případě položky typu structure (Value type).
        /// </summary>
        /// <param name="item"></param>
        public void Remove(T item)
        {
            this._Items.RemoveAll(i => Object.ReferenceEquals(i, item));
        }
        /// <summary>
        /// Odebere položku na uveeném indexu.
        /// </summary>
        /// <param name="index"></param>
        public void RemoveAt(int index)
        {
            this._Items.RemoveAt(index);
        }
        /// <summary>
        /// Smaže všechny položky.
        /// </summary>
        public void Clear()
        {
            this._Items.Clear();
        }
        /// <summary>
        /// Přidá další položku, privátní výkonná metoda
        /// </summary>
        /// <param name="item"></param>
        private void _ItemAdd(T item)
        {
            this.CheckItemBeforeAdd(item);
            string name = this.GetName(item);
            if (!String.IsNullOrEmpty(name) && !this.IgnoreNameDuplicity && this.ContainsName(name))
            {   // Máme-li jméno, a neměli bychom ignorovat duplicitu, pak ověříme, zda v kolekci máme / nemáme položku daného jména:
                throw new DevException("Položka jménem %0 je uvedena více než jednou.", name);
            }
            this._OnItemAddBefore(item);
            this._Items.Add(item);
            this._OnItemAddAfter(item);
        }
        /// <summary>
        /// Odebere položku, privátní výkonná metoda
        /// </summary>
        /// <param name="item"></param>
        private void _ItemRemove(T item)
        {
            this._OnItemRemoveBefore(item);
            this._Items.Add(item);
            this._OnItemRemoveAfter(item);
        }
        #endregion
        #region Konverze
        /// <summary>
        /// Pomocí konvertoru (parametr convertor) převede zdejší záznamy na záznamy cílové, a vrátí jejich seznam.
        /// </summary>
        /// <typeparam name="R"></typeparam>
        /// <param name="convertor"></param>
        /// <returns></returns>
        public List<R> Convert<R>(Func<T, R> convertor)
        {
            return this.Convert<R>(null, convertor);
        }
        /// <summary>
        /// Pomocí konvertoru (parametr convertor) převede zdejší záznamy na záznamy cílové, a vrátí jejich seznam.
        /// </summary>
        /// <typeparam name="R"></typeparam>
        /// <param name="convertor"></param>
        /// <returns></returns>
        public List<R> Convert<R>(Func<T, bool> filter, Func<T, R> convertor)
        {
            List<R> result = new List<R>();
            foreach (T item in this._Items)
            {
                if (filter == null || (filter != null && filter(item)))
                    result.Add(convertor(item));
            }
            return result;
        }
        #endregion
        #region IEnumerable
        IEnumerator<T> IEnumerable<T>.GetEnumerator() { return this._Items.GetEnumerator(); }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this._Items.GetEnumerator(); }
        #endregion
    }
}
